﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.QueryParsers;
using Lucene.Net.Search;
using Lucene.Net.Store;
using Directory = Lucene.Net.Store.Directory;
using Version = Lucene.Net.Util.Version;

namespace Autocomplete.Net
{
    public class Autocompleter : IAutocompleter
    {
        private readonly Directory _directory;
        private readonly StandardAnalyzer _analyzer;

        public Autocompleter(string directory)
        {
            _directory = FSDirectory.Open(new DirectoryInfo(directory + "/autocomplete"));

            _analyzer = new StandardAnalyzer(Version.LUCENE_29);
        }

        public void CreateIndex(IEnumerable<Item> items)
        {
            Index(items, true);
        }

        public void UpdateIndex(IEnumerable<Item> items)
        {
            Index(items, false);
        }

        private void Index(IEnumerable<Item> items, bool create)
        {
            var writer = new IndexWriter(_directory, _analyzer, create, IndexWriter.MaxFieldLength.LIMITED);

            foreach (var item in items.Select(x => x).Distinct())
            {
                var document = CreateDocument(item);
                writer.AddDocument(document);
            }

            writer.Optimize();
            writer.Close();
        }

        private static Document CreateDocument(Item item)
        {
            var document = new Document();
            document.Add(new Field("name", item.Name, Field.Store.YES, Field.Index.ANALYZED));
            document.Add(new Field("type", item.Type, Field.Store.YES, Field.Index.ANALYZED));
            return document;
        }

        public string[] Search(string query)
        {
            return Search(query, null);
        }

        public string[] Search(string query, string[] typesToBoost)
        {
            try
            {
                var reader = IndexReader.Open(_directory, true);
                var searcher = new IndexSearcher(reader);
                var queryParser = new MultiFieldQueryParser(Version.LUCENE_29, new[] { "name", "type" }, _analyzer);

                var queryBuilder = new StringBuilder();
                if (typesToBoost != null && typesToBoost.Length > 0)
                {
                    foreach (var typeToBoost in typesToBoost)
                    {
                        queryBuilder.AppendFormat("({0}* +type:{1}^2.0)", BuildPrefixQuery(query), typeToBoost);
                        queryBuilder.Append(" OR ");
                    }
                }
                queryBuilder.AppendFormat("({0}*)", BuildPrefixQuery(query));

                var q = queryParser.Parse(queryBuilder.ToString());
                var topDocs = searcher.Search(q, 10);
                return topDocs.scoreDocs.Select(doc => searcher.Doc(doc.doc).Get("name")).ToArray();
            }
            catch (Exception)
            {
                return new string[] { };
            }
        }

        private static string BuildPrefixQuery(string query)
        {
            var queryText = new StringBuilder();

            if (!string.IsNullOrWhiteSpace(query))
            {
                string[] tokens = query.Split(' ');
                foreach (var token in tokens)
                {
                    queryText.AppendFormat("+{0}* ", token);
                }
            }

            return queryText.ToString().Trim();
        }
    }
}
